package image;

import image.mapper.PixelImageMapper;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.image.BufferedImage;
import java.awt.image.ColorModel;
import java.util.Arrays;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import list.functions.ListFunctions;
import math.mathExtended.MathExtended;
import userInterface.TextOutput;
import processor.images.ImageProcessor;
import time.Timer;

/**
 *
 * Fully Tested: No
 * Fully Documented: No
 * Created: 20-May-2012
 * Last Updated: 20-May-2012
 *
 * @author Shimu Wu
 */
public class PixelImage {

    /**
     * The number of rows in the matrix.
     */
    private int height;

    /**
     * The number of columns in the matrix.
     */
    private int width;

    /**
     * The data in the matrix
     */
    private int[] pixels;

    public PixelImage(int width, int height) {
        this.width = width;
        this.height = height;
        this.pixels = new int[width * height];
    }

    public PixelImage(BufferedImage img) {
        this.width = img.getWidth();
        this.height = img.getHeight();
        this.pixels = getRGB(img, 0, 0, width, height);
    }

    /**
     * @return the height
     */
    public int getHeight() {
        return height;
    }

    /**
     * @return the width
     */
    public int getWidth() {
        return width;
    }

    public final int get(int x, int y) {
        return this.get(x + y * width);
    }

    public final int get(int index) {
        return this.pixels[index];
    }

    public final void set(int x, int y, int value) {
        this.set(x + y * width, value); 
    }

    public final void set(int index, int value) {
        this.pixels[index] = value;
    }

    public boolean equals(PixelImage other) {
        if ((this.width == other.width)
                && (this.height == other.height)) {
            return ListFunctions.equals(Arrays.asList(this.pixels),
                    Arrays.asList(other.pixels));
        }
        return false;
    }

    public PixelImage map(PixelImageMapper mapper) {
        PixelImage out = new PixelImage(width, height);

        for (int x = 0; x < width; x++) {
            for (int y = 0; y < height; y++) {
                out.set(x, y, mapper.process(x, y, this));
            }
        }
        return out;
    }

    public void map(BufferedImage img, PixelImageMapper mapper) {
        Graphics g = img.createGraphics();
        for (int x = 0; x < img.getWidth(); x++) {
            for (int y = 0; y < img.getHeight(); y++) {
                g.setColor(new Color(mapper.process(x, y, this)));
                g.drawLine(x, y, x, y);
            }
        }
    }

    /**
     * Given one or more ImageProcessors, process this PixelImage and return
     * a new PixelImage.
     * 
     * @param processors
     * @return 
     */
    public PixelImage process(PixelImageProcessor... processors) {

        PixelImage newImg = this;
        TextOutput printer = new TextOutput(true);
        printer.println();
        Timer timer = new Timer(printer);

        for (PixelImageProcessor p : processors) {
            timer.start(p.getDescription());
            newImg = p.process(newImg);
            timer.stop("finished processing using: " + p.toString());
        }
        return newImg;
    }

    public final void print() {
        for (int x = 0; x < width; x++) {
            for (int y = 0; y < height; y++) {
                System.out.print(pixels[x + y * width] + ", ");
            }
            System.out.println();
        }
    }

    public BufferedImage createCompatibleDestImage(BufferedImage src,
            ColorModel dstCM) {
        if (dstCM == null) {
            dstCM = src.getColorModel();
        }
        return new BufferedImage(dstCM,
                dstCM.createCompatibleWritableRaster(src.getWidth(),
                src.getHeight()), dstCM.isAlphaPremultiplied(), null);
    }

    /**
     * A convenience method for getting ARGB data from an image. 
     * This tries to avoid the performance penalty of BufferedImage.getRGB 
     * unmanaging the image.
     */
    private int[] getRGB(BufferedImage image, int x, int y,
            int width, int height) {
        int[] data = new int[width * height];

        int type = image.getType();
        if (type == BufferedImage.TYPE_INT_ARGB
                || type == BufferedImage.TYPE_INT_RGB) {
            return (int[]) image.getRaster().getDataElements(x, y,
                    width, height, data);
        }
        return image.getRGB(x, y, width, height, data, 0, width);
    }

    /**
     * A convenience method for setting ARGB pixels in an image. 
     * This tries to avoid the performance penalty of BufferedImage.setRGB 
     * unmanaging the image.
     */
    private void setRGB(BufferedImage image, int x, int y,
            int width, int height, int[] pixels) {
        int type = image.getType();
        if (type == BufferedImage.TYPE_INT_ARGB
                || type == BufferedImage.TYPE_INT_RGB) {
            image.getRaster().setDataElements(x, y, width, height, pixels);
        } else {
            image.setRGB(x, y, width, height, pixels, 0, width);
        }
    }

    /**
     * Returns a BufferedImage containing the contents of this PixelImage.
     * @return 
     */
    public BufferedImage toBufferImage() {
        BufferedImage img = new BufferedImage(width, height,
                BufferedImage.TYPE_INT_ARGB);
        setRGB(img, 0, 0, width, height, pixels);
        return img;
    }

    /**
     * Returns an Icon containing the contents of this PixelImage scaled
     * to the appropriate size.
     * 
     * @param boundingWidth
     * @param boundingHeight
     * @return 
     */
    public Icon toIcon(int boundingWidth, int boundingHeight) {
        return new ImageIcon(ImageProcessor.scaleToFit(toBufferImage(),
                boundingWidth, boundingHeight, java.awt.Image.SCALE_SMOOTH));
    }

    /**
     * Returns a copy of this PixelImage. Modifications to the newly
     * returned PixelImage will NOT be reflected in this PixelImage.
     * 
     * @return 
     */
    public PixelImage getCopy() {
        PixelImage newImg = new PixelImage(width, height);
        System.arraycopy(this.pixels, 0, newImg.pixels, 0, pixels.length);
        return newImg;
    }
}
